<?php
namespace my;

/**
 * 微信支付
 */
class WxPay
{
    protected $config;
    public $err_msg = '支付出错，请联系客服';

    public function __construct($conf = [])
    {
        $config = [
            'appid'      => '',//微信开放平台上的应用ID
            'mch_id'     => '',//商户号
            'api_key'    => '',//微信商户平台上自己设定的api密钥32位
            'notify_url' => '',//自定义回调地址
            'trade_type' => 'APP',
            'openid'     => '',
        ];
        $this->config = array_merge($config, $conf);
    }

    //获取预支付订单
    public function xcx_unifiedOrder($out_trade_no, $total_fee, $body = '')
    {
        $url  = "https://api.mch.weixin.qq.com/pay/unifiedorder";
        $data = [
            'appid'              => $this->config['appid'],
            'mch_id'             => $this->config['mch_id'],
            'nonce_str'          => $this->getRandChar(32),
            'body'               => $body ? : $out_trade_no,//商品描述128字符
            'out_trade_no'       => $out_trade_no,
            'total_fee'          => round($total_fee * 100),//以分为单位
            'spbill_create_ip'   => $this->get_client_ip(),
            'notify_url'         => $this->config['notify_url'],
            'trade_type'         => $this->config['trade_type'],
            'openid'             => $this->config['openid']
        ];

        $data["sign"] = $this->getSign($data);
        $xml          = $this->arrayToXml($data);
        $response     = $this->postXmlCurl($xml, $url);
        if ($response) {
            $res = $this->xmlstr_to_array($response);
            if ($res['return_code'] == 'SUCCESS') {
                if ($res['result_code'] == 'SUCCESS') {
                    return $this->xcx_getOrder($res['prepay_id']);
                }
                $this->err_msg = $res['err_code'].':'.$res['err_code_des'];
            }
            $this->err_msg = $res['return_msg'];
        }
        return false;
    }

    //退款订单
    public function weixin_unifiedOrder_refund($out_trade_no, $refund_fee, $body = '' , $cityname)
    {
        //微信退款接口
        $url  = "https://api.mch.weixin.qq.com/secapi/pay/refund";
        $data = [
            'appid'              => $this->config['appid'],
            'mch_id'             => $this->config['mch_id'],
            'nonce_str'          => $this->getRandChar(32),
            // 'body'               => $body ? : $out_trade_no,//商品描述128字符
            'out_trade_no'       => $out_trade_no,
            'refund_fee'         => round(((float)$refund_fee) * 100),//以分为单位
            'total_fee'          => round(((float)$refund_fee) * 100),//以分为单位
            'out_refund_no'      => $out_trade_no,
        ];
        $data["sign"] = $this->getSign($data);
        $xml          = $this->arrayToXml($data);
        $response     = $this->postXmlCurl_refund($xml, $url, 30, $cityname);
        if ($response) {
            $res = $this->xmlstr_to_array($response);
            if ($res['return_code'] == 'SUCCESS' && $res['return_msg'] == "OK") {
                return $res;
            }
            $this->err_msg = $res['return_msg'];
        }
        return false;
    }

    //银联支付退款订单
    public function xcx_unifiedOrder_refund($out_trade_no, $refund_fee, $body = '', $cityname, $refundAmount=0, $merOrderId='')
    {
        //微信退款接口
        $url  = "https://qr.chinaums.com/netpay-route-server/api/";
        $orderIDrand = rand();
        $data = [
            "msgType"               => "refund",
            "signType"              => "SHA256", 
            "requestTimestamp"      => date('Y-m-d H:i:s'),
            "msgSrc"                => "WWW.CHZLHKCJT.COM", //
            "mid"                   => $this->config['mid'], 
            "tid"                   => $this->config['tid'], 
            "merOrderId"            => $merOrderId,
            "refundAmount"          => round($refundAmount*100),
        ];
        $data["sign"] = $this->unionpay_getSign($data);
        return $data;
    }

    //获取预支付订单
    public function unifiedOrder($out_trade_no, $total_fee, $body = '')
    {
        $url  = "https://api.mch.weixin.qq.com/pay/unifiedorder";
        $data = [
            'appid'              => $this->config['appid'],
            'mch_id'             => $this->config['mch_id'],
            'nonce_str'          => $this->getRandChar(32),
            'body'               => $body ? : $out_trade_no,//商品描述128字符
            'out_trade_no'       => $out_trade_no,
            'total_fee'          => round($total_fee * 100),//以分为单位
            'spbill_create_ip'   => $this->get_client_ip(),
            'notify_url'         => $this->config['notify_url'],
            'trade_type'         => $this->config['trade_type'],
        ];

        $data["sign"] = $this->getSign($data);
        $xml          = $this->arrayToXml($data);
        $response     = $this->postXmlCurl($xml, $url);

        if ($response) {
            $res = $this->xmlstr_to_array($response);
            if ($res['return_code'] == 'SUCCESS') {
                if ($res['result_code'] == 'SUCCESS') {
                    return $this->getOrder($res['prepay_id']);
                }
                $this->err_msg = $res['err_code'].'：'.$res['err_code_des'];
            }
            $this->err_msg = $res['return_msg'];
        }
        return false;
    }

    //银联云闪付获取预支付订单
    public function unionpay_yun_unifiedOrder($out_trade_no, $total_fee, $body = '')
    {
        $orderIDrand = rand(1000, 9999);
        $data = [
            "msgType"               => "uac.appOrder",  //
			"requestTimestamp"      => date('Y-m-d H:i:s'),  //
			"msgSrc"                => "WWW.CHZLHKCJT.COM",  // "WWW.CHZLHKCJT.COM" //
			"mid"                   => $this->config['mid'], 
			"tid"                   => $this->config['tid'], 
			"instMid"               => "APPDEFAULT",  //
			"signType"              => "SHA256",
			"merOrderId"            => "10F1".$orderIDrand."LHKC".$out_trade_no,  //订单编号
			"totalAmount"           => round($total_fee * 100),
			"tradeType"             => 'APP',
            "notifyUrl"             => $this->config['notify_url'],  //回调地址
        ];
        $data["sign"] = $this->unionpay_getSign($data);
        return $data;
    }

    //银联主动检查订单支付状态
    public function unionpay_recheck_unifiedOrder($merOrderId)
    {
        // "msgType": "query",
        // "requestTimestamp": "2020-02-26 17:06:20",
        // "msgSrc": "WWW.TEST.COM",
        // "mid": "898310148160568",
        // "tid": "88880001",
        // "merOrderId": "319420200225061415075318399213",
        // "sign": "2D782B7C59394A7ACA7CC32C51577221"

        //  10F1_2312523561580940_155
        $data = [
            "msgType"               => "query", //
			"requestTimestamp"      => date('Y-m-d H:i:s'), //
			"msgSrc"                => "WWW.CHZLHKCJT.COM", //
            "signType"              => "SHA256", 
			"mid"                   => $this->config['mid'], 
			"tid"                   => $this->config['tid'], 
			"merOrderId"            => $merOrderId, //订单编号
        ];
        $data["sign"] = $this->unionpay_getSign($data);
        return $data;
    }

    //银联获取预支付订单
    public function unionpay_unifiedOrder($out_trade_no, $total_fee, $body = '')
    {
        $orderIDrand = mt_rand(10,1000);
        $data = [
            "msgType"               => "wx.unifiedOrder", //
			"requestTimestamp"      => date('Y-m-d H:i:s'), //
			"msgSrc"                => "WWW.CHZLHKCJT.COM", //
			"mid"                   => $this->config['mid'], 
			"tid"                   => $this->config['tid'], 
			"instMid"               => "MINIDEFAULT", //
			"signType"              => "SHA256", 
            "srcReserve"            => $body, 
			"merOrderId"            => "10F1_".$out_trade_no."_".$orderIDrand, //订单编号
			"totalAmount"           => round($total_fee * 100), 
			"tradeType"             => 'MINI', 
			"subAppId"              => 'wxcb7a20d43bacdbc2', //微信分配的子商户公众账号ID
            "subOpenId"             => $this->config['openid'], //用户子标识
            "notifyUrl"             => $this->config['notify_url'], //回调地址
        ];
        $data["sign"] = $this->unionpay_getSign($data);
        return $data;
    }

    //生成签名
    function unionpay_getSign($Obj)
    {
        foreach ($Obj as $k => $v) {
            $Parameters[$k] = $v;
        }
        //签名步骤一：按字典序排序参数
        ksort($Parameters);
        $String = $this->unionpay_formatBizQueryParaMap($Parameters, false);
        //签名步骤二：在string后加入KEY
        $String = $String . "8kBFmEb3wP4kFQ4hHpWWkSHD6ZbtZQD23mPh3ebc7GbAFNFy";
        //签名步骤三：MD5加密
        return hash('sha256', $String);
    }

    //生成签名
    function getSign($Obj)
    {
        foreach ($Obj as $k => $v) {
            $Parameters[strtolower($k)] = $v;
        }
        //签名步骤一：按字典序排序参数
        ksort($Parameters);
        $String = $this->formatBizQueryParaMap($Parameters, false);
        //签名步骤二：在string后加入KEY
        $String = $String . "&key=" . $this->config['api_key'];
        //签名步骤三：MD5加密
        return strtoupper(md5($String));
    }

    //生成签名
    function xcx_getSign($Obj)
    {
        foreach ($Obj as $k => $v) {
            $Parameters[$k] = $v;
        }
        //签名步骤一：按字典序排序参数
        ksort($Parameters);
        $String = $this->xcx_formatBizQueryParaMap($Parameters, false);
        //签名步骤二：在string后加入KEY
        $String = $String . "&key=" . $this->config['api_key'];
        //签名步骤三：MD5加密
        return md5($String);
    }

    //执行第二次签名，才能返回给客户端使用
    public function getOrder($prepayId)
    {
        $data = [
            'appid'     => $this->config['appid'],
            'partnerid' => $this->config['mch_id'],
            'prepayid'  => $prepayId,
            'package'   => 'Sign=WXPay',
            'noncestr'  => $this->getRandChar(32),
            'timestamp' => time(),
        ];

        $data["sign"]   = $this->getSign($data);
        return $data;
    }

    //执行第二次签名，才能返回给客户端使用
    public function xcx_getOrder($prepayId)
    {
        $data = [
            'appId'     => $this->config['appid'],
            'nonceStr'  => $this->getRandChar(32),
            'package'   => 'prepay_id=' . $prepayId,
            'signType'  => 'MD5',
            'timeStamp' => time()
        ];
        $data["paySign"] = $this->xcx_getSign($data);
        return $data;
    }

    //获取回调接口返回信息
    public function get_xml_obj()
    {
        $postStr = file_get_contents('php://input');
        $postObj = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
        return $postObj;
    }

    //对象转数组
    public function object_to_array($array)
    {
        if (is_object($array)) {
            $array = (array)$array;
        }
        if (is_array($array)) {
            foreach ($array as $key => $value) {
                $array[$key] = $this->object_to_array($value);
            }
        }
        return $array;
    }

    //获取指定长度随机字符串
    function getRandChar($length)
    {
        $str    = null;
        $strPol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
        $max    = strlen($strPol) - 1;
        for ($i = 0; $i < $length; $i++) {
            $str .= $strPol[rand(0, $max)];
        }
        return $str;
    }

    //获取当前服务器的IP
    function get_client_ip()
    {
        if ($_SERVER['REMOTE_ADDR']) {
            $cip = $_SERVER['REMOTE_ADDR'];
        } elseif (getenv("REMOTE_ADDR")) {
            $cip = getenv("REMOTE_ADDR");
        } elseif (getenv("HTTP_CLIENT_IP")) {
            $cip = getenv("HTTP_CLIENT_IP");
        } else {
            $cip = "unknown";
        }
        return $cip;
    }

    //xml转成数组
    function xmlstr_to_array($xmlstr)
    {
        $doc = new \DOMDocument();
        $doc->loadXML($xmlstr);
        return $this->domnode_to_array($doc->documentElement);
    }

    //xml转成数组
    function xmlstr_to_array_unionpay($xmlstr)
    {
        $arr = explode("&",$xmlstr);
        $data = [];
        foreach ($arr as $key => $val) {
            $aa = explode("=", $val);
            $data[$aa[0]] = $aa[1];
        }
        return $data;
    }

    //数组转xml
    function arrayToXml($arr)
    {
        $xml = "<xml>";
        foreach ($arr as $key => $val) {
            if (is_numeric($val))
                $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
            else
                $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
        }
        $xml .= "</xml>";
        return $xml;
    }

    //将数组转成uri字符串
    function xcx_formatBizQueryParaMap($paraMap, $urlencode)
    {
        $buff = "";
        ksort($paraMap);
        foreach ($paraMap as $k => $v) {
            if ($urlencode) {
                $v = urlencode($v);
            }
            $buff .= $k . "=" . $v . "&";
        }
        $reqPar = '';
        if (strlen($buff) > 0) {
            $reqPar = substr($buff, 0, strlen($buff) - 1);
        }
        return $reqPar;
    }

    //银联_将数组转成uri字符串
    function unionpay_formatBizQueryParaMap($paraMap, $urlencode)
    {
        $buff = "";
        ksort($paraMap);
        foreach ($paraMap as $k => $v) {
            if ($urlencode) {
                $v = urlencode($v);
            }
            $buff .= $k . "=" . $v . "&";
        }
        $reqPar = '';
        if (strlen($buff) > 0) {
            $reqPar = substr($buff, 0, strlen($buff) - 1);
        }
        return $reqPar;
    }

    //将数组转成uri字符串
    function formatBizQueryParaMap($paraMap, $urlencode)
    {
        $buff = "";
        ksort($paraMap);
        foreach ($paraMap as $k => $v) {
            if ($urlencode) {
                $v = urlencode($v);
            }
            $buff .= strtolower($k) . "=" . $v . "&";
        }
        $reqPar = '';
        if (strlen($buff) > 0) {
            $reqPar = substr($buff, 0, strlen($buff) - 1);
        }
        return $reqPar;
    }

    //post https请求，CURLOPT_POSTFIELDS xml格式
    function postXmlCurl($xml, $url, $second = 30)
    {
        //初始化curl
        $ch = curl_init();
        //超时时间
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        //这里设置代理，如果有的话
        //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
        //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
        //设置header
        curl_setopt($ch, CURLOPT_HEADER, FALSE);
        //要求结果为字符串且输出到屏幕上
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        //post提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        //运行curl
        $data = curl_exec($ch);
        //返回结果
        if ($data) {
            curl_close($ch);
            return $data;
        } else {
            $error = curl_errno($ch);
            $this->err_msg = 'Curl出错，错误码：'. $error;
            //echo "<a href='http://curl.haxx.se/libcurl/c/libcurl-errors.html'>错误原因查询</a>";
            curl_close($ch);
            return false;
        }
    }

    function postXmlCurl_refund($xml, $url, $second = 30, $cityname)
    {
        //初始化curl
        $ch = curl_init();
        //超时时间
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        //这里设置代理，如果有的话
        //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
        //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
        //设置header
        curl_setopt($ch, CURLOPT_HEADER, FALSE);
        //要求结果为字符串且输出到屏幕上
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
	
        //设置证书
        //使用证书：cert 与 key 分别属于两个.pem文件
        //证书文件请放入服务器的非web目录下
        $sslCertPath = dirname(__FILE__).'/cert/apiclient_cert_'.$cityname.'.pem';
        $sslKeyPath = dirname(__FILE__).'/cert/apiclient_key_'.$cityname.'.pem';
        curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
        curl_setopt($ch, CURLOPT_SSLCERT, $sslCertPath);
        curl_setopt($ch, CURLOPT_SSLKEYTYPE,'PEM');
        curl_setopt($ch, CURLOPT_SSLKEY, $sslKeyPath);
        //post提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        //运行curl
        $data = curl_exec($ch);
        //返回结果
        if ($data) {
            curl_close($ch);
            return $data;
        } else {
            $error = curl_errno($ch);
            $this->err_msg = 'Curl出错，错误码：'. $error;
            //echo "<a href='http://curl.haxx.se/libcurl/c/libcurl-errors.html'>错误原因查询</a>";
            curl_close($ch);
            return false;
        }
    }

    function domnode_to_array($node)
    {
        $output = array();
        switch ($node->nodeType) {
            case XML_CDATA_SECTION_NODE:
            case XML_TEXT_NODE:
                $output = trim($node->textContent);
                break;
            case XML_ELEMENT_NODE:
                for ($i = 0, $m = $node->childNodes->length; $i < $m; $i++) {
                    $child = $node->childNodes->item($i);
                    $v = $this->domnode_to_array($child);
                    if (isset($child->tagName)) {
                        $t = $child->tagName;
                        if (!isset($output[$t])) {
                            $output[$t] = array();
                        }
                        $output[$t][] = $v;
                    } elseif ($v) {
                        $output = (string)$v;
                    }
                }
                if (is_array($output)) {
                    if ($node->attributes->length) {
                        $a = array();
                        foreach ($node->attributes as $attrName => $attrNode) {
                            $a[$attrName] = (string)$attrNode->value;
                        }
                        $output['@attributes'] = $a;
                    }
                    foreach ($output as $t => $v) {
                        if (is_array($v) && count($v) == 1 && $t != '@attributes') {
                            $output[$t] = $v[0];
                        }
                    }
                }
                break;
        }
        return $output;
    }
}